package kawigi.language;

import java.io.File;

import kawigi.util.StringsUtil;


/**
 * Class introducing language-dependent features for Visual Basic.
 * Class is made as singleton.
 *
 * @see     EditorLanguage
 */
public final class VBLang extends EditorLanguage
{
	/**
	 * Single instance of this class
	 */
	private static final VBLang inst = new VBLang();

	/**
	 * Counter for array variables in code of test case executor. It's necessary
	 * because we have to declare counter i before first cycle and then use it
	 * in all successive cycles.
	 */
	private int arrayNum = 0;

	/**
	 * Returns single instance of the class.
	 *
	 * @return      Instance of <code>VBLang</code>
	 */
	public static VBLang getInstance()
	{
		return inst;
	}

	/**
	 * Default constructor.
	 */
	private VBLang()
	{
		super();
		// Fill the map of our type names
		fillTypeNames(getAllTypeNames());
		// And fill all code-generation and local compiling variables which are
		// special for Visual Basic.
		sDefaultFileName = "$PROBLEM$.vb";
		sDefaultCompileCommand = "vbc $PROBLEM$.vb";
		sDefaultExecuteCommand = '/' == File.separatorChar? "mono $PROBLEM$.exe": "$CWD$\\$PROBLEM$.exe";
		sLineComment = "'";
		sPrintStrAdd = " & ";
		sLineEnd = "";
		sIfStat = "If ";
		sThenStat = " Then";
		sElseStat = "Else";
		sElseIfStat = "ElseIf ";
		sEndIfStat = "End If";
		sForEndStat = "Next";
		sArrayIndLeft = '(';
		sArrayIndRight = ')';
		sTabString = "Chr(9)";
		sQuoteString = "\"\"\"\"";
		sEqualSign = " = ";
		sUnequalSign = " <> ";
		sLogicalOr = " Or ";
		sLogicalAnd = " And ";
		sLogicalNot = "Not ";
		sFuncEnd = "End Function";
		sMainSubEnd = "End Sub";
		sStringsAdd = " &";
		sLineContinued = " _";
	}

	/*
	  ============================================================================
		Common procedures.
	 */
	/**
	 * Returns array of all type names in VB. Type names order is related to
	 * order of values in <code>EditorDataType</code>.
	 *
	 * @return      Array of type names.
	 *
	 * @see         EditorDataType
	 */
	static String[] getAllTypeNames()
	{
		return new String[] {"String", "Integer", "Double", "Long", "Boolean", "String()", "Integer()", "Double()", "Long()"};
	}

	/**
	 * Returns the name of this language in lowercase ('vb').
	 * This name is used in name of category in properties for this language.
	 *
	 * @return      Name of this language - 'vb'
	 */
	public String toString()
	{
		return "vb";
	}
	//============================================================================

	/*
	  ============================================================================
		Methods related to tag expansion in templates and templates themselves.
	 */
	/**
	 * Add to source code taken from TopCoder server testing-code tag in place where
	 * it should be normally.
	 * 
	 * @param source	Source code given by TopCoder server
	 * @return			Source code that should be shown at the code pane
	 */
	public String addAutoTestTag(String source)
	{
		int line_ind = source.lastIndexOf('\n');
		if (line_ind != -1 && line_ind == source.length() - 1) {
			line_ind = source.lastIndexOf('\n', line_ind - 1);
		}
		if (line_ind != -1 && line_ind < source.length() - 1 && source.charAt(line_ind + 1) == '\'')
		{
			line_ind = source.lastIndexOf('\n', line_ind - 1);
			if (line_ind != -1) {
				source = source.substring(0, line_ind + 1) + EditorLanguage.sTestingCodeTag
							+ source.substring(line_ind + 1);
			}
		}
		
		return source;
	}
	//============================================================================

	/*
	  ============================================================================
		Code related to test code generation.
	 */
	/**
	 * Clears all data stored in class for generating new pack of test code.
	 *
	 * @see     EditorLanguage#clear()
	 */
	protected void clear()
	{
		super.clear();
		arrayNum = 0;
	}

	/**
	 * Inserts directive for test region starting and starts the main module
	 * of test program.
	 *
	 * @see     EditorLanguage#preamble()
	 */
	protected void preamble()
	{
		text("#Region \"Testing code generated by KawigiEdit\"").endLine();
		text("Module MainModule").endLine();
		indentRight();
	}

	/**
	 * Inserts directive for test region ending and ends the main module of test
	 * program.
	 *
	 * @see     EditorLanguage#postamble()
	 */
	protected void postamble()
	{
		indentLeft().text("End Module").endLine();
		text("#End Region").endLine();
	}

	/**
	 * Function for inserting prefix of function header.
	 *
	 * @param funcRetType   The return type of function
	 * @return              All methods of <code>EditorLanguage</code> related to test code generation
	 *                      return <code>this</code> to make possible convinient call chains
	 *
	 * @see                 #funcDefPostfix(EditorDataType)
	 * @see                 EditorLanguage#funcDefPrefix(EditorDataType)
	 * @see                 EditorLanguage#funcDefPostfix(EditorDataType)
	 */
	protected EditorLanguage funcDefPrefix(EditorDataType funcRetType)
	{
		return text("Function ");
	}

	/**
	 * Function for inserting postfix of function header -
	 * the part that is put after closing parenthesis after parameters list.
	 * By default puts open curly bracket and finishes the code line
	 * 'cause this is all that needed in most of the languages.
	 * Method must put space character at the beginning if it's needed to separate
	 * function parameters' parenthesis and further text. Also finishing of
	 * code line and indenting further text is mandatory in this method.
	 *
	 * @param funcRetType   The return type of function
	 *
	 * @see                 #funcDefPrefix(EditorDataType)
	 * @see                 EditorLanguage#funcDefPrefix(EditorDataType)
	 * @see                 EditorLanguage#funcDefPostfix(EditorDataType)
	 */
	protected void funcDefPostfix(EditorDataType funcRetType)
	{
		text(" As ").text(getTypeName(funcRetType)).endLine();
		indentRight();
	}

	/**
	 * Puts parameter of the function to its definition (not call).
	 *
	 * @param paramName     Name of function parameter
	 * @param paramType     Type of function parameter
	 * @return              All methods of <code>EditorLanguage</code> related to test code generation
	 *                      return <code>this</code> to make possible convinient call chains
	 *
	 * @see                 EditorLanguage#funcDefParam(String, EditorDataType)
	 */
	protected EditorLanguage funcDefParam(String paramName, EditorDataType paramType)
	{
		return text("ByVal ").text(paramName).text(" As ").text(getTypeName(paramType));
	}

	/**
	 * Starts iteration over some array variable.
	 *
	 * @param arrayName     Name of the variable to be iterated
	 *
	 * @see                 EditorLanguage#iterFirstLine(String)
	 */
	protected void iterFirstLine(String arrayName)
	{
		++arrayNum;
		// Adding declaration of i only if this is first array in function
		if (1 == arrayNum)
			text("Dim i As Integer").endCodeLine();
		text("For i = 0 To ").text(arrayName).text(sArrayLenFunc).text(" - 1").endLine();
		indentRight();
	}

	/**
	 * Declares the variable. Variable can be any of builtin types or
	 * a class of problem-solver object.
	 *
	 * @param varName   Name of the variable to be declared.
	 * @param typeName  Type of the variable to be declared.
	 *
	 * @see                 EditorLanguage#varDeclare(CharSequence, CharSequence)
	 */
	protected void varDeclare(CharSequence varName, CharSequence typeName)
	{
		text("Dim ").text(varName).text(" As ").text(typeName).endCodeLine();
	}

	/**
	 * Method for adding code of remembering current time.
	 *
	 * @param varName   Name of variable to remember time in.
	 *
	 * @see             EditorLanguage#rememberCurTime(String)
	 */
	protected void rememberCurTime(String varName)
	{
		text("Dim ").text(varName).text(" As DateTime").endCodeLine();
		text(varName).text(" = DateTime.Now").endCodeLine();
	}

	/**
	 * Method for adding formula counting difference between two
	 * time points stored in two different variables in seconds (double value).
	 *
	 * @param varStart  Variable name of starting moment
	 * @param varEnd    Variable name of ending moment
	 * @return          All methods of <code>EditorLanguage</code> related to test code generation
	 *                  return <code>this</code> to make possible convinient call chains
	 *
	 * @see             EditorLanguage#timeDiff(String, String)
	 */
	protected EditorLanguage timeDiff(String varStart, String varEnd)
	{
		return text(varEnd).text(".Subtract(").text(varStart).text(").TotalSeconds");
	}

	/**
	 * Adds header of main procedure.
	 *
	 * @see             EditorLanguage#mainSubDef()
	 */
	protected void mainSubDef()
	{
		text("Sub Main()");
		super.mainSubDef();
	}

	/**
	 * Escapes all special characters in string constants or string-array constants.
	 * VB don't understand escape-sequences, so we have to deal with quotes in
	 * completely different way.
	 *
	 * @param val   In input it's constant to be escaped, in output it's
	 *              escaped constant ready for inserting in code.
	 * @param type  Data type represented by given constant.
	 *
	 * @see         #unescapeSequences(StringBuilder, EditorDataType)
	 * @see         EditorLanguage#escapeSequences(StringBuilder, EditorDataType)
	 */
	protected void escapeSequences(StringBuilder val, EditorDataType type)
	{
		assert type.isString();
		boolean isInQuote = false;
		for (int i = 0; val.length() > i; ++i) {
			char c = val.charAt(i);
			if ('\\' == c) {
				// If we in quotes then this is an array and quotes and backslashes
				// will be already escaped. So we need to undo this escaping.
				if (isInQuote && val.length() > i + 1) {
					if ('"' == val.charAt(i + 1)) {
						val.setCharAt(i, '"');
						++i;
					}
					else if ('\\' == val.charAt(i + 1))
						val.deleteCharAt(i + 1);
				}
			}  // if ('\\' == c)
			else if ('"' == c) {
				// Quote for array means start or end of the element, for string
				// it needs to be escaped by another quote
				if (type.isArrayType()) {
					isInQuote = !isInQuote;
				}
				else {
					val.insert(i, '"');
					++i;
				}
			}  // if ('"' == c), if ('\\' == c) else
		}  // for (int i = 0; val.length() > i; ++i)
	}
	//============================================================================

	/*
	  ============================================================================
		Code related to test code parsing and test cases extraction.
	 */
	/**
	 * Removes all escapings in string and string-array constants
	 * made by <code>escapeSequences</code>.
	 *
	 * @param val   In input it's escaped constant taken from code, in output
	 *              it's constant ready for showing to end-user
	 * @param type  Data type represented by given constant
	 *
	 * @see         #escapeSequences(StringBuilder, EditorDataType)
	 * @see         EditorLanguage#unescapeSequences(StringBuilder, EditorDataType)
	 */
	protected void unescapeSequences(StringBuilder val, EditorDataType type)
	{
		assert type.isString();
		boolean isInQuote = false;
		for (int i = 0; val.length() > i; ++i) {
			char c = val.charAt(i);
			if ('\\' == c) {
				// If we met backslash in quotes then it is array and our system needs
				// it to be escaped
				if (isInQuote) {
					val.insert(i, '\\');
					++i;
				}
			}  // if ('\\' == c)
			else if ('"' == c) {
				boolean isArray = type.isArrayType();
				if (isArray && !isInQuote) {
					// If we met quote in array but not inside quote, then it is
					// starting of new array element
					isInQuote = true;
				}
				else if (val.length() > i + 1 && '"' == val.charAt(i + 1)) {
					// If we have double quotes then it is single quote inside string.
					// We need to escape quote in array ('cause our system forces
					// to do so) or leave single quote in simple string
					if (isInQuote) {
						val.setCharAt(i, '\\');
						++i;
					}
					else
						val.deleteCharAt(i);
				}
				else if (isArray) {
					// If this is array and we here then this is an ending
					// of array element
					isInQuote = false;
				}
			}  // if ('"' == c), if ('\\' == c) else
		}  // for (int i = 0; val.length() > i; ++i)
	}

	/**
	 * Gets regular expression for finding test case parameter initialization.
	 * For VB it takes care of multiline strings: string constant must not end
	 * with '_'.
	 *
	 * @param num       Number of parameter
	 * @param type      Type of parameter
	 * @return          Regular expression for finding this parameter in code
	 *
	 * @see             EditorLanguage#getTestParamRegex(int, EditorDataType)
	 */
	protected String getTestParamRegex(int num, EditorDataType type)
	{
		String res;
		if (type.isString() && !type.isArrayType())
			res = sTestParamVarPrefix + num + " = (.*?[^_])" + StringsUtil.sCRLFregex;
		else
			res = super.getTestParamRegex(num, type);
		return res;
	}
}
